//
//  OYAuthenticator.m
//  ObjectiveYouTube
//
//  Created by Tristan O'Tierney on 3/14/08.
//  Copyright 2008 Tristan O'Tierney. All rights reserved.
//

#import <ObjectiveYouTube/ObjectiveYouTube.h>

// Error constants
static NSString *kOYAuthInvalidLoginErrorDomain = @"OYAuthInvalidLoginError";
const static int kOYAuthInvalidLoginErrorCode = 200;
static NSString *kOYAuthNoSourceErrorDomain = @"OYAuthNoSourceError";
const static int kOYAuthNoSourceErrorCode = 200;
static NSString *kOYAuthNoUserOrPasswordErrorDomain = @"OYAuthNoUserOrPasswordError";
const static int kOYAuthNoUserOrPasswordErrorCode = 300;

// General constants
const static NSTimeInterval kOYAuthTimeoutInterval = 60.0f;

// Login string constants
static NSString *kOYAuthLoginURL = @"https://www.google.com/youtube/accounts/ClientLogin";
static NSString *kOYAuthContentType = @"application/x-www-form-urlencoded";
static NSString *kOYAuthPostBody = @"Email=%@&Passwd=%@&service=youtube&source=%@";
static NSString *kOYAuthResponseTokenPrefix = @"Auth=";
static NSString *kOYAuthResponseUserPrefix = @"YouTubeUser=";

@interface OYAuthenticator (Private)
- (void)_delegateError:(NSString *)domain withCode:(int)code;
- (void)_delegateError:(NSError *)error;
- (void)_cleanupConnection;
@end

@implementation OYAuthenticator
#pragma mark -
#pragma mark Initialization
#pragma mark -

- (id)initWithDelegate:(id)delegate clientID:(NSString *)clientID developerKey:(NSString *)developerKey source:(NSString *)source {
	if ((self = [super init])) {
		[self setDelegate:delegate];
		[self setClientID:clientID];
		[self setDeveloperKey:developerKey];
		[self setSource:source];
		
		_loginConnection = nil;
		_responseData = nil;
		_username = nil;
		_token = nil;
		_timeoutInterval = kOYAuthTimeoutInterval;
	}
	
	return self;
}

- (void)dealloc {
	_delegate = nil;
	
	[self logout];

	[_developerKey release];
	_developerKey = nil;
	[_clientID release];
	_clientID = nil;
	[_source release];
	_source = nil;
	
	[super dealloc];
}

#pragma mark -
#pragma mark Public Methods
#pragma mark -

- (void)loginAsUser:(NSString *)username withPassword:(NSString *)password {
	// cancel any url connection and cleanup it's mess
	[self logout];
	
	// User must have a source first
	if (!_source) {
		return [self _delegateError:kOYAuthNoSourceErrorDomain
						   withCode:kOYAuthNoSourceErrorCode];
	} else if (!username || ![username length] || !password || ![password length]) {
		return [self _delegateError:kOYAuthNoUserOrPasswordErrorDomain
						   withCode:kOYAuthNoUserOrPasswordErrorCode];
	}
	
	// set the url/headers
	NSURL *loginURL = [NSURL URLWithString:kOYAuthLoginURL];
	NSMutableURLRequest *loginRequest = [[NSMutableURLRequest alloc] initWithURL:loginURL];
	[loginRequest setValue:kOYAuthContentType forHTTPHeaderField:@"Content-Type"];
	[loginRequest setTimeoutInterval:_timeoutInterval];
	[loginRequest setHTTPMethod:@"POST"];
	
	// generate the post body
	NSString *encodedUser = [username stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
	NSString *encodedPassword = [password stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
	NSString *encodedSource = [_source stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
	NSString *body = [NSString stringWithFormat:kOYAuthPostBody, encodedUser, encodedPassword, encodedSource];
	[loginRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
	
	_loginConnection = [NSURLConnection connectionWithRequest:loginRequest delegate:self];
}

- (void)logout {
	if (_loginConnection) {
		[_loginConnection cancel];
		_loginConnection = nil;
	}
	
	[_username release];
	_username = nil;
	
	[_token release];
	_token = nil;
	
	[self _cleanupConnection];
}

- (void)cancelLogin {
	[self logout];
}

#pragma mark -
#pragma mark Accessors / Mutators
#pragma mark -

- (BOOL)isLoggingIn {
	return _loginConnection != nil;
}

- (BOOL)isLoggedIn {
	// This assumes you didn't manually set the token/username to something
	// non-nil.  If you did, and they're invalid, you will get errors :)
	return (_username != nil && _token != nil);
}

- (NSString *)username {
	return _username;
}

- (void)setUsername:(NSString *)username {
	[username retain];
	[_username release];
	_username = username;
}

- (NSString *)token {
	return _token;
}

- (void)setToken:(NSString *)token {
	[token retain];
	[_token release];
	_token = token;
}

- (id)delegate {
	return _delegate;
}

- (void)setDelegate:(id)delegate {
	_delegate = delegate;
}

- (NSString *)clientID {
	return _clientID;
}

- (void)setClientID:(NSString *)clientID {
	[clientID retain];
	[_clientID release];
	_clientID = clientID;
}

- (NSString *)developerKey {
	return _developerKey;
}

- (void)setDeveloperKey:(NSString *)developerKey {
	[developerKey retain];
	[_developerKey release];
	_developerKey = developerKey;
}

- (NSString *)source {
	return _source;
}

- (void)setSource:(NSString *)source {
	[source retain];
	[_source release];
	_source = source;
}

- (NSTimeInterval)timeoutInterval {
	return _timeoutInterval;
}

- (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
	_timeoutInterval = timeoutInterval;
}

#pragma mark -
#pragma mark Callbacks
#pragma mark -

// URL connection callbacks
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
	if (!_responseData) {
		_responseData = [[NSMutableData alloc] init];
	}
	
	[_responseData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
	NSString *token = nil;
	NSString *username = nil;

	// parse the response
	NSString *responseString = [[[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding] autorelease];
	
	if (responseString) {
		NSString *tokenBuffer = nil;
		NSString *usernameBuffer = nil;
		NSScanner *scanner = [NSScanner scannerWithString:responseString];
		
		if ([scanner scanString:kOYAuthResponseTokenPrefix intoString:nil] &&
			[scanner scanUpToString:@"\n" intoString:&tokenBuffer]) {
			token = tokenBuffer;
		}
		
		if ([scanner scanString:kOYAuthResponseUserPrefix intoString:nil] &&
			[scanner scanUpToString:@"\n" intoString:&usernameBuffer]) {
			username = usernameBuffer;
		}
	}
	
	// clean up the connection
	[self _cleanupConnection];
		
	// let our delegate know we're finished
	if (token && username) {
		[self setToken:token];
		[self setUsername:username];
		
		if (_delegate && [_delegate respondsToSelector:@selector(authenticator:succededWithToken:)]) {
			[_delegate authenticator:self succededWithToken:token];
		}
	} else {
		[self _delegateError:kOYAuthInvalidLoginErrorDomain
					withCode:kOYAuthInvalidLoginErrorCode];
	}
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
	// clean up the connection
	[self _cleanupConnection];
	
	// let our delegate know we're finished
	[self _delegateError:kOYConnectionFailedErrorDomain
				withCode:kOYConnectionFailedErrorCode];
}
@end

@implementation OYAuthenticator (Private)
- (void)_delegateError:(NSString *)domain withCode:(int)code {
	NSError *error = [NSError errorWithDomain:domain
										 code:code
									 userInfo:nil];
	
	[self _delegateError:error];
}

- (void)_delegateError:(NSError *)error {
	if (_delegate && [_delegate respondsToSelector:@selector(authenticator:failedWithError:)]) {
		[_delegate authenticator:self failedWithError:error];
	}
}

- (void)_cleanupConnection {
	_loginConnection = nil;
	[_responseData release];
	_responseData = nil;
}
@end
